Sección A
La Agencia Digital de Innovación Pública tiene disponibles los datos georeferenciados de las carpetas de
investigación aportados por la PGJ. La tabla está disponible aquí:
https://datos.cdmx.gob.mx/explore
1) ¿Qué pruebas identificarías para asegurar la calidad de estos datos? No es necesario hacerlas. Sólo describe la prueba y qué te dice cada una.
# DataFrame.sample(7) #Para conocer cómo están estructurados mis datos
# DataFrame.dtypes #Para saber si los tipos de datos coinciden con la información mostrada en cada columna
# DataFrame.isnull().sum()/df_pgj.shape[0] *100 #Para obtener el porcentaje de cuántas celdas están vacias por cada columna
# DataFrame.describe() #Muestra valores estadísticos en los campos numéricos
# DataFrame.corr() #Es posible que se pueda retirar información si la correlación es alta ente columnas
2) ¿Cuántos delitos registrados hay en la tabla? ¿Qué rango de tiempo consideran los datos?
# importar las librerías necesarias
import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
import re
# se importa la información
df_pgj = pd.read_csv('carpetas-pgj.csv')
# primero se revisan las columnas que hay en el dataframe
print(df_pgj.columns)
# los tipos de datos que tiene el dataframe
print("\n",df_pgj.dtypes)
# y una muestra para ver la forma de los datos
df_pgj.sample(5)
# esta celda se añade después de haber procesado la mayoría de los datos.
# se vuelve evidente que hay información en la base de datos que no pertenece a la CDMX
# por lo cual se depura la base
# datos obtenidos de https://www.cdmxpolitico.com/p/alcaldias.html
alcaldias_oficiales = ["ALVARO OBREGON","AZCAPOTZALCO","BENITO JUAREZ","COYOACAN","CUAJIMALPA","CUAUHTEMOC",
"GUSTAVO A MADERO","IZTACALCO","IZTAPALAPA","MAGDALENA CONTRERAS","MIGUEL HIDALGO",
"MILPA ALTA","TLAHUAC","TLALPAN","VENUSTIANO CARRANZA","XOCHIMILCO"]
df_pgj = df_pgj.loc[df_pgj["alcaldia_hechos"].isin(alcaldias_oficiales)]
# aplicar "len" y "unique" para obtener la información
print(f"La tabla muestra una cantidad total de {len(df_pgj.delito)} delitos con {len(df_pgj.delito.unique())} tipos únicos.")
print("La base de datos comprende delitos desde {} y hasta {}.".format(int(df_pgj.ao_hechos.min()),int(df_pgj.ao_hechos.max())))
3) ¿Cómo se distribuye el número de delitos en la CDMX? ¿Cuáles son los 5 delitos más frecuentes?
# primer se intenta responder la pregunta directamente, pero no hay una columna que cuente la información de manera clara
# por lo tanto se añade una columna llamada "contador" que contiene "1",
# de esta manera se obtiene una columna que puede servir para contar los datos de cualquier otra columna
df_pgj["contador"]=1
# se aplica "groupby" que nos permite agrupar las filas del dataframe por cada valor único en la columna "delito"
# después de agruparlos, se extraen solamente los datos de la columna "contador" a una serie llamada "top_5"
top_5 = df_pgj.groupby("delito").count()["contador"]
# se ordena la serue en forma descendente, donde el nombre del delito sirve como índice
top_5.sort_values(ascending=False)[:5]
4) Identifica los delitos que van a la alza y a la baja en la CDMX en el último año (ten cuidado con los delitos con pocas ocurrencias).
# se elige "mayor a 2018" y "menor a 2020" para obtener solamente los datos de 2019
# se incorpora sólo la suficiente información para responder la pregunta a un dataframe nuevo
delitos_ab = df_pgj[(df_pgj["ao_hechos"]>2018)&(df_pgj["ao_hechos"]<2020)][["mes_hechos","delito","contador"]]
delitos_ab.reset_index(inplace=True)
# al resetar el index, los datos anteriores se guardan en una nueva columna que descartamos
delitos_ab.drop('index',axis=1,inplace=True)
# comparar enero con diciembre, si diciembre es mayor entonces guardar el tipo de delito en lista crimenes_alza
# guardar delito en crimenes_baja si enero es mayor a diciembre
# para evitar los delitos con pocas incidencias, se guardan solamente aquellos donde la diferencia es mayor a 50
crimenes = df_pgj.delito.unique()
crimenes_alza = []
crimenes_baja = []
for i in range(len(crimenes)):
a = delitos_ab[(delitos_ab.delito==crimenes[i])&(delitos_ab.mes_hechos=="Enero")]["contador"].count()
b = delitos_ab[(delitos_ab.delito==crimenes[i])&(delitos_ab.mes_hechos=="Diciembre")]["contador"].count()
if (b-a)>50:
crimenes_alza.append([crimenes[i],b-a])
elif (a-b)>50:
crimenes_baja.append([crimenes[i],a-b])
crimenes_alza
crimenes_baja
5) ¿Cuál es la alcaldía que más delitos tiene y cuál es la que menos?.¿Por qué crees que sea esto?
delitos_alcaldia = df_pgj.groupby("alcaldia_hechos").count()["contador"]
# se ordena la serie en forma descendente, donde el nombre del delito sirve como índice
print(f"""mayor número de delitos:\n{delitos_alcaldia.sort_values(ascending=False)[:1]}
menor número de delitos:
{delitos_alcaldia.sort_values(ascending=False)[-1:]}\n
En la delegación Cuauhtemoc se concentran la generación de recursos que permanecen en la misma delegación,
junto con varias zonas acomodadas de la CDMX, además de que está rodeada en 3 direcciones por el circuito
interior y la atraviesa la av. insurgentes.
La delegación Milpa Alta, en cambio, se localiza al sur de la CDMX en una zona con menos facilidades de
transportación y cuya población se desplaza a otras partes para trabajar.""")
6) Dentro de cada alcaldía, cuáles son las tres colonias con más delitos
top3_cols = df_pgj.groupby(["alcaldia_hechos","colonia_hechos"]).agg({'contador':sum})
g = top3_cols["contador"].groupby(level=0, group_keys=False)
res = g.apply(lambda x: x.sort_values(ascending=False).head(3))
res
7) ¿Existe alguna tendencia estacional en la ocurrencia de delitos (mes, semana, día de la semana, quincenas)?
meses = df_pgj.groupby(["mes_hechos"]).count()["contador"]
mes_list = ["Enero","Febrero","Marzo","Abril","Mayo","Junio",
"Julio","Agosto","Septiembre","Octubre","Noviembre","Diciembre"]
# loop para ordenar los meses de acuerdo a calendario
orden = []
for m in range(len(meses)):
for n in range(len(mes_list)):
if meses.keys()[m] == mes_list[n]:
orden.append([mes_list[m],meses[n]])
ord_df = pd.DataFrame(orden,columns=["meses","n_delitos"])
ord_df.plot(figsize=(12,4))
plt.title("Tendencia estacional de delitos: \nJulio y Octubre tienen menor actividad\nEnero, Abril y Diciembre tienen mayor actividad")
plt.show()
8) ¿Cuales son los delitos que más caracterizan a cada alcaldía? Es decir, delitos que suceden con mayor frecuencia en una alcaldía y con menor frecuencia en las demás.
top3_cols = df_pgj.groupby(["alcaldia_hechos","delito"]).agg({'contador':sum})
g = top3_cols["contador"].groupby(level=1, group_keys=False)
res = g.apply(lambda x: x.sort_values(ascending=False)[:1])
res
9) Calcula el número de homicidios dolosos por cada 100 mil habitantes anual para cada Área Geoestadística Básica (AGEB) del INEGI. (hint: no importa que el dato de población no esté actualizado).
a) Pinta un mapa con este indicador. Describe los resultados.
temp4 = df_pgj[df_pgj.categoria_delito=="HOMICIDIO DOLOSO"]
temp5 = temp4.groupby("alcaldia_hechos").count()
temp5[temp5.contador>50]["contador"]/100000
10) ¿Cómo diseñarías un indicador que midiera el nivel “inseguridad”? Diséñalo al nivel de desagregación que te parezca más adecuado (ej. manzana, calle, AGEB, etc.).
# diseño básico de "delitos totales" por "calle_hechos"
print(""" +demo+
+------------------------+------------------------+
|calle_hechos |delitos_totales |
+------------------------+------------------------+
|IZTAPALAPA |44007 |
|TLAHUAC |29702 |
|TLALPAN |7691 |
+------------------------+------------------------+
""")
11) Con alguna de las medidas de crimen que calculaste en los incisos anteriores, encuentra patrones de concentración geográfica de delitos (hint: puedes usar algoritmos de Machine Learning no supervisados).
a) ¿Qué caracteriza a cada punto de concentración de delitos y qué tienen en común?
12) Toma los delitos clasificados como “Robo a pasajero a bordo de transporte público con y sin violencia”. ¿Cuáles son las ruta de transporte público donde más ocurren estos delitos?
Sección B
B1. QQP Descarga la Base de datos histórica de Quién es Quién en los Precios de Profeco y resuelve los siguientes incisos. Para el procesamiento de los datos y el análisis exploratorio debes debes usar Spark SQL, preferentemente, con Python.
a. ¿Cuántos registros hay?
from pyspark import SparkConf
from pyspark.context import SparkContext
from pyspark.sql import SparkSession
spark = SparkSession.builder.master("local[*]").appName("ds_exam").getOrCreate()
pysparkDF = spark.read.csv("profeco.csv",header=True)
pysparkDF.printSchema()
print("Hay {:,} de registros.".format(pysparkDF.count()))
b. ¿Cuántas categorías?
print("Hay {:,} categorias.".format(pysparkDF.select("categoria").distinct().count()))
c. ¿Cuántas cadenas comerciales están siendo monitoreadas?
print("Se monitorean {:,} cadenas comerciales.".format(pysparkDF.select("cadenaComercial").distinct().count()))
d. ¿Cómo podrías determinar la calidad de los datos? ¿Detectaste algún tipo de inconsistencia o error en la fuente?
# Se podría determinar la calidad de los datos buscando valores faltantes, nulos, duplicados o mal escritos
# entender si hay mucha o poca correlación entre las columnas
# y revisar que coincida el tipo de datos con lo que se muestra en cada columna
e. ¿Cuáles son los productos más monitoreados en cada entidad?
pysparkDF.orderBy("estado").groupBy("estado","producto").count().show(truncate=False)
f. ¿Cuál es la cadena comercial con mayor variedad de productos monitoreados?
temtem = pysparkDF.groupBy("cadenaComercial").count().collect()
res = max(temtem, key = lambda i : i[1])
print("La cadena con más productos monitoreados es {} con {:,}".format(res[0],res[1]))
a. Genera una canasta de productos básicos que te permita comparar los precios geográfica y temporalmente. Justifica tu elección y procedimiento
canasta = ["MAIZ","FRIJOL","ARROZ","AZUCAR","HARINA","ACEITE","ATUN","SARDINA","LECHE","CHILE"]
# se eligen los primeros 10 productos de los recomendados por el gobierno: https://www.gob.mx/canastabasica
b. ¿Cuál es la ciudad más cara del país?¿Cuál es la más barata?
# los datos no incluyen referencias por ciudad, pero si por estado
canastilla = pysparkDF.groupBy(["estado","producto","precio"]).count().collect()
cancan = pd.DataFrame(canastilla,columns=["estado","producto","count","precio"])
cd_cara = cancan.groupby("estado").count()["precio"]
print("La ciudad más cara está en {}\ny la más barata está en {}.".format(
cd_cara.sort_values(ascending=False)[:1],
cd_cara.sort_values(ascending=False)[-4:-3]))
c. ¿Hay algún patrón estacional entre años?
# se realiza una reducción en el tamaño de los datos para poder procesar la información con mayor facilidad
redu = pysparkDF["fechaRegistro","precio"]
pat_est = redu.groupBy(["fechaRegistro"]).count().collect()
temp1 = pd.DataFrame(pat_est,columns=["fechaRegistro","count"])
temp1["año"]=temp1.fechaRegistro.apply(lambda x: x[:4])
temp1["mes"]=temp1.fechaRegistro.apply(lambda x: x[5:7])
temp1 = temp1.drop("fechaRegistro",axis=1)
fig, ax = plt.subplots(figsize=(15,7))
temp1.groupby(['mes']).count().plot(ax=ax)
plt.title("Estacionalidad en Precios")
plt.show()
d. ¿Cuál es el estado más caro y en qué mes?
redu2 = pysparkDF["fechaRegistro","estado","precio"]
pdf = redu2.toPandas()
temp2 = pd.DataFrame(redu2,columns=["fechaRegistro","estado","precio"])
temp2["año"]=temp1.fechaRegistro.apply(lambda x: x[:4])
temp2["mes"]=temp1.fechaRegistro.apply(lambda x: x[5:7])
temp2 = temp2.drop("fechaRegistro",axis=1)
temp2
e. ¿Cuáles son los principales riesgos de hacer análisis de series de tiempo con estos datos?
# previo al uso extensivo de tecnología, el cual aún se encuentra en adopción, los registros se llevaban
# de forma manual, por lo cual sería frecuente encontrar discrepancias entre los datos según se retroceda en el tiempo.
a. Genera un mapa que nos permita identificar la oferta de categorías en la zona metropolitana de León Guanajuato y el nivel de precios en cada una de ellas. Se darán puntos extra si el mapa es interactivo.
# primero se filtran los datos para el área metropolitana de León, Gto.
edo_gto = pysparkDF[pysparkDF["estado"] == "GUANAJUATO"]
mun_leon = pysparkDF[(pysparkDF["municipio"] == "LEON")|(pysparkDF["municipio"] == "LEÓN")]
print("Hay {:,} registros en León, Gto.".format(mun_leon.count()))
# se importa la librería "folium" porque permite desplazarse en el mapa y modificar el acercamiento.
# se hará uso de la librería "random" ya que no es factible cargar tantos marcadores en el mapa
import folium
from folium.plugins import HeatMap
from random import sample
# se convierte a DataFrame de pandas para mayor facilidad manipulando las coordenadas
temp3 = mun_leon.select("*").toPandas()
# selección de columnas y limpieza de datos nulos
mun_leon_df = temp3[["categoria","precio","latitud","longitud"]]
mun_leon_df.dropna(axis=0,how="any",inplace=True)
mun_leon_df.dtypes
mun_leon_df.isnull().sum()
mun_leon_df.isna().sum()
# se retiran valores "NA" para obtener un dataframe más limpio
try:
leon_limpio = mun_leon_df[(mun_leon_df["latitud"]!="NA")|(mun_leon_df["longitud"]!="NA")]
leon_limpio.reset_index(inplace=True)
leon_limpio.drop("index",axis=1,inplace=True)
except:
leon_limpio.head(3)
leon_limpio.head(3)
# se crea un lista con las variables deseadas para llamarlas en la creación del mapa y los marcadores
listilla = list(zip(leon_limpio.latitud.values,leon_limpio.longitud.values,leon_limpio.categoria,leon_limpio.precio))
# se define un punto donde centrar el mapa
hmap_all = folium.Map(location=[21.1323074,-101.6500494], zoom_start=12, )
hm_wide_all = HeatMap( list(zip(leon_limpio.latitud.values, leon_limpio.longitud.values)),
min_opacity=0.15,
max_val=1000000,
radius=15,
blur=15,
max_zoom=1,
)
# se genera una muestra aleatoria para insertar marcadores en el mapa
ale = sample(listilla,800)
for i in range(800):
folium.Marker([ale[i][0], ale[i][1]],
popup=f'Producto encontrado:\n{ale[i][2]}\nPrecio promedio:{ale[i][3]}',
).add_to(hmap_all)
hmap_all.save('OPI_map.html')
hmap_all.add_child(hm_wide_all)